<!--
 * @Author: Jackie
 * @Date: 2021-08-22 02:08:46
 * @LastEditors: Jackie
 * @LastEditTime: 2021-10-18 17:29:54
 * @Description:
-->
<template>
  <div class="main">
    <div class="stage">
      <div class="snake">
        <div />
      </div>
      <div class="food">
        <div />
        <div />
        <div />
        <div />
      </div>
    </div>
    <div class="pointInfo">
      <div class="score">SCORE: <span class="score-item">0</span></div>
      <div class="level">LEVEL:<span class="level-item">1</span></div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'

@Component({
  name: 'Account',
})
export default class extends Vue {
  public created(): void {
    this.createdClass()
  }
  public createdClass(): void {
    this.$nextTick(() => {
      class Food {
        element: HTMLElement

        constructor() {
          this.element = document.querySelector('.food')!
        }
        get X() {
          return this.element.offsetLeft
        }
        get Y() {
          return this.element.offsetTop
        }
        change() {
          //x y坐标0~290且为10倍数的整数
          const left = Math.round(Math.random() * 29) * 10
          const top = Math.round(Math.random() * 29) * 10
          this.element.style.left = left + 'px'
          this.element.style.top = top + 'px'
        }
      }

      class ScorePanel {
        score = 0
        level = 1
        scoreEle: HTMLElement
        levelEle: HTMLElement
        maxLevel: number
        upScore: number

        constructor(maxLevel: number = 10, upScore: number = 10) {
          this.maxLevel = maxLevel
          this.upScore = upScore
          this.scoreEle = document.querySelector('.score-item')!
          this.levelEle = document.querySelector('.level-item')!
        }
        addScore() {
          this.scoreEle.innerHTML = ++this.score + ''
          if (this.level < this.maxLevel) {
            this.levelUp()
          }
        }
        levelUp() {
          if (this.score % this.upScore === 0) {
            this.levelEle.innerHTML = ++this.level + ''
          }
        }
      }

      class Snake {
        snake: HTMLElement
        head: HTMLElement
        bodies: HTMLCollection

        constructor() {
          this.snake = document.querySelector('.snake')!
          this.head = document.querySelector('.snake>div')!
          this.bodies = this.snake.getElementsByTagName('div')
        }
        get X() {
          return this.head.offsetLeft
        }
        get Y() {
          return this.head.offsetTop
        }
        set X(value: number) {
          if (value < 0 || value > 290) {
            throw new Error('蛇撞墙了！')
          }

          if (
            this.bodies[1] &&
            (this.bodies[1] as HTMLElement).offsetLeft === value
          ) {
            if (value > this.X) {
              value = this.X - 10
            } else {
              value = this.X + 10
            }
          }
          this.move()
          this.head.style.left = value + 'px'
          this.checkHeadBody()
        }
        set Y(value: number) {
          if (value < 0 || value > 290) {
            throw new Error('蛇撞墙了！')
          }
          if (
            this.bodies[1] &&
            (this.bodies[1] as HTMLElement).offsetTop === value
          ) {
            if (value > this.Y) {
              value = this.Y - 10
            } else {
              value = this.Y + 10
            }
          }
          this.move()
          this.head.style.top = value + 'px'
          this.checkHeadBody()
        }
        addBody() {
          const div = document.createElement('div')
          this.snake.appendChild(div)
        }
        move() {
          for (let i = this.bodies.length - 1; i > 0; i--) {
            let Y = (this.bodies[i - 1] as HTMLElement).offsetTop
            let X = (this.bodies[i - 1] as HTMLElement).offsetLeft
            ;(this.bodies[i] as HTMLElement).style.top = Y + 'px'
            ;(this.bodies[i] as HTMLElement).style.left = X + 'px'
          }
        }
        checkHeadBody() {
          for (let i = 1; i < this.bodies.length; i++) {
            let db = this.bodies[i] as HTMLElement
            if (this.X === db.offsetLeft && this.Y === db.offsetTop) {
              throw new Error('撞到自己了！')
            }
          }
        }
      }

      class GameControl {
        food: Food
        scorepanel: ScorePanel
        snake: Snake
        direction: string = ''
        isLive = true

        constructor() {
          this.food = new Food()
          this.scorepanel = new ScorePanel(10, 1)
          this.snake = new Snake()

          this.init()
        }

        init() {
          document.addEventListener('keydown', this.keydownHander.bind(this)) //用bind把this绑定在类中
          this.run()
        }
        keydownHander(e: KeyboardEvent) {
          this.direction = e.key
        }
        run() {
          this.checkEat(this.snake.X, this.snake.Y)
          try {
            switch (this.direction) {
              case 'ArrowUp':
                this.snake.Y -= 10
                break
              case 'ArrowDown':
                this.snake.Y += 10
                break
              case 'ArrowLeft':
                this.snake.X -= 10
                break
              case 'ArrowRight':
                this.snake.X += 10
                break
            }
          } catch (error) {
            alert(error.message + 'Game Over 刷新以重新开始')
            this.isLive = false
          }

          this.isLive &&
            setTimeout(
              this.run.bind(this),
              300 - (this.scorepanel.level - 1) * 30
            )
        }
        checkEat(X: number, Y: number) {
          if (X === this.food.X && Y === this.food.Y) {
            this.food.change()
            this.scorepanel.addScore()
            this.snake.addBody()
          }
        }
      }

      new GameControl()
    })
  }
}
</script>
<style>
/* 
  style开启scoped后会是节点带有data-v-7427b2c6字段，
  而snake类中的addBody方法在添加蛇身节点时，
  其添加的节点没有data-v-7427b2c6字段
  导致添加的节点不渲染，不知道咋整，暂时先不开启scoped 
*/
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
.main {
  width: 360px;
  height: 420px;
  border-radius: 10px;
  border: 10px solid #000;
  margin: 100px auto;
  background-color: #b7d4a8;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-items: center;
}
.main .stage {
  width: 304px;
  height: 304px;
  border: 2px solid #000;
  position: relative;
}
/* .stage .snake {
} */
.stage .snake div {
  position: absolute;
  top: 0;
  left: 0;
  width: 10px;
  height: 10px;
  /* display: inline-block; */
  background-color: #000;
  border: 1px solid #b7d4a8;
}
.stage .food {
  width: 10px;
  height: 10px;
  position: absolute;
  top: 50px;
  left: 100px;
  display: flex;
  justify-content: space-around;
  align-content: space-around;
  flex-flow: row wrap;
}
.food div {
  width: 4px;
  height: 4px;
  background-color: #000;
  transform: rotate(45deg);
}
.main .pointInfo {
  display: flex;
  justify-content: space-between;
  width: 90%;
  font-size: 20px;
  font-weight: bold;
  font-family: 'Courier';
}
</style>
